package dkm;

import java.awt.Color;
import robocode.HitByBulletEvent;
import robocode.Robot;
import robocode.Rules;
import robocode.ScannedRobotEvent;
import robocode.WinEvent;

/**
 * This robot runs to the nearest wall and then moves along the walls.
 * Inspired by Walls.java made by Mathew A. Nelson and Robocode contributors.
 * I have added my own personal touches.
 * @author David Mau
 */
public class PersonalWall extends Robot {

  private boolean onTrack = false;
  private double maxMove;
  private boolean randomReverse = false;
  private double turnAngle = 90.0;
  private boolean lockOn = false;
  private int shotAt = 0;

  /**
   * The first thing that the robot will do.
   * @see robocode.Robot#run()
   */
  public void run() {
    setBodyColor(Color.red);
    setRadarColor(Color.white);
    setGunColor(Color.gray);
    maxMove = getBattleFieldHeight();
    if ( maxMove < getBattleFieldWidth() ) {
      maxMove = getBattleFieldWidth();
    }
    
    while (true) {
      // If the robot isn't moving along the wall...
      if ( !onTrack ) {
        goToNearestWall();
        turnGunRight(90.0);
        turnRight(90.0);
      }

      // Move the robot along the wall.
      ahead(maxMove);
      shotAt = 0;
      turnRight(turnAngle);
      
      // Randomly decide if the robot should suddenly change directions at a corner.
      // 20% of occurrence.
      if ( Math.random() < 0.2 ) {
        turnRight(turnAngle);
        turnAngle = turnAngle * -1.0;
        randomReverse = !randomReverse;
        turnGunRight(180.0);
      }
    }
    
  }
  
  /**
   * This method makes the Robot go to the wall closest to it.
   */
  public void goToNearestWall() {
    // Figure out which wall is the closest to the robot.
    double closeX = getBattleFieldWidth() - getX();
    double closeY = getBattleFieldHeight() - getY();

    // If the robot is in the lower left area of the map...
    if ( getX() <= closeX && getY() <= closeY ) {
      
      // If the left wall is closer
      if ( getX() < getY() ) {
        // Turn to the left wall and move to there
        turnRight( efficientRightTurnTo( this.getHeading(), 270.0) );
        ahead( getX() );
      }
      // Otherwise the bottom wall is closer
      else {
        // Turn to the south wall and move to there
        turnRight( efficientRightTurnTo( this.getHeading(), 180.0) );
        ahead( getY() );
      }
      
    }
    // If the robot is in the lower right area of the map...
    else if ( getX() > closeX && getY() <= closeY ) {
      
      // If the right wall is closer
      if ( closeX < getY() ) {
        // Turn to the right wall and move to there
        turnRight( efficientRightTurnTo( this.getHeading(), 90.0) );
        ahead( closeX );
      }
      // Otherwise the bottom wall is closer
      else {
        // Turn to the south wall and move to there
        turnRight( efficientRightTurnTo( this.getHeading(), 180.0) );
        ahead( getY() );
      }
      
    }
    // If the robot is in the upper right area of the map...
    else if ( getX() <= closeX && getY() > closeY ) {
      
      // If the left wall is closer
      if ( getX() < closeY ) {
        // Turn to the left wall and move to there
        turnRight( efficientRightTurnTo( this.getHeading(), 270.0) );
        ahead( getX() );
      }
      // Otherwise the top wall is closer
      else {
        // Turn to the top wall and move to there
        turnRight( efficientRightTurnTo( this.getHeading(), 0.0) );
        ahead( closeY );
      }
      
    }
    // Otherwise, the robot is in the top right area of the map.
    else {
      
      // If the left wall is closer
      if ( closeX < closeY ) {
        // Turn to the right wall and move to there
        turnRight( efficientRightTurnTo( this.getHeading(), 90.0) );
        ahead( closeX );
      }
      // Otherwise the top wall is closer
      else {
        // Turn to the top wall and move to there
        turnRight( efficientRightTurnTo( this.getHeading(), 0.0) );
        ahead( closeY );
      }
      
    }
    
    onTrack = true;
  }
  
  /**
   * This method calculates the fewest degrees to turn in a direction.
   * Keep in mind it uses 0 as North, 90 as East, 180 as South, and 270 as West.
   * @param heading the current heading of the robot
   * @param destinationAngle the angle the Robot should turn to.  0 is North, 90 is East, etc.
   * @return the amount the gun should turn right in to reach that angel
   */
  public double efficientRightTurnTo( double heading, double destinationAngle ) {
    double result = destinationAngle - heading;

    // If the result is greater than 180, then the robot could turn faster
    // By turning right a negative number.
    if ( result > 180.0 ) {
      result = result - 360.0;
    }
    // If the result is less than -180, then the robot could turn faster
    // By turning right a positive number.
    else if ( result < -180.0 ) {
      result = 360.0 + result;
    }

    return result;
  }

  
  /**
   * When the robot scans another robot, it should fire a calculated shot and lock onto the robot.
   * @see robocode.Robot#onScannedRobot(robocode.ScannedRobotEvent)
   * @param event information about the scanned robot
   */
  public void onScannedRobot(ScannedRobotEvent event) {
    // Figure out how much power to use, and fire.
    fire( powerBasedOnDistance( event.getDistance() ) );
    
    // If we're stopped, fire with everything the robot has.
    lockOn = true;
    
    // See if that robot is still ahead of the robot.
    scan();
    
    // If the enemy is gone, move on.
    lockOn = false;
  }
  
  /**
   * This method dictates the shot power that the robot will use based on how far away
   * the enemy is.
   * @param distance how far away the enemy robot is
   * @return how much power should go into the shot
   */
  public double powerBasedOnDistance( double distance ) {
    double result = Rules.MAX_BULLET_POWER;
    double exponentialDistance = distance - 20.0;
    exponentialDistance = Math.sqrt(exponentialDistance) * 0.1;
    
    // If the robot is 20 pixels or more in front of the robot, don't use full power.
    // Adjust the power in an exponential fashion.
    // However, if we are stopped and locked on, ignore this.  Full Power!
    if ( distance > 20.0 && !lockOn ) {
      result = result - exponentialDistance;
    }
    
    // If the result is below the minimum bullet power, set it to the minimum.
    if ( result < Rules.MIN_BULLET_POWER) {
      result = Rules.MIN_BULLET_POWER;
    }
    
    return result;
  }
  
  /**
   * When the robot is hit by a bullet while moving along a wall, it should remember being hit.
   * If the robot was hit already while moving along a wall, turn to the parallel wall.
   * @see robocode.Robot#onHitByBullet(robocode.HitByBulletEvent)
   * @param event information about being hit by a bullet
   */
  public void onHitByBullet(HitByBulletEvent event) {
    // If the robot wasn't hit before, remember this event.
    // Ignore shots that hit the robot if the robot is locked on.
    if ( shotAt == 0 && !lockOn ) {
      shotAt++;
    }
    // If the robot got hit twice, odds are a counter Walls-type robot is
    // hunting the robot.  Take action to move to the other wall to dodge the
    // hunter.
    // Note: Causes the robot to move in an arc.  Interesting to say the least.
    // It was originally a bug, but is now a feature.  Perhaps utilizing events
    // could be the way to do multiple actions at the same time for Robot objects...
    else if ( shotAt > 1 && !lockOn ) {
      shotAt++;
      turnRight(turnAngle);
      ahead(maxMove);
      shotAt = 0;
    }
    else {
      // Do nothing.  The robot should be moving to the other wall.
      shotAt++;
    }
  }
  
  /**
   * When the robot wins, stop and spin the gun.
   * @see robocode.Robot#onWin(robocode.WinEvent)
   * @param event information about the win
   */
  public void onWin(WinEvent event) {
    stop();
    turnGunRight( 1440.0 );
  }
  
  /**
   * This method sets the robot to be locked on.
   * It is used for debugging and testing purposes mostly.
   */
  public void setLockedOn() {
    lockOn = true;
  }

  /**
   * This method returns the current state of PersonalWall.
   * If the variable returned is true, PersonalWall is moving in the opposite direction
   * that it normally does.
   * If the variable returned is false, PersonalWall is moving in it's usual direction.
   * @return the state of PersonalWall's movement
   */
  public boolean getRandomReverse() {
    return randomReverse;
  }
  
}
